use crate::bitboard::BitBoard;
use crate::board::Board;
use crate::board::Pos;
use crate::color::Color;
use crate::moves::Move;
use crate::piece::PieceType;

use IterState::*;

enum IterState {
    NextPieceType,
    Iterating,
}

pub struct MoveGen<'a> {
    board: &'a mut Board,
    side_to_move: Color,
    iter_state: IterState,
    next_piece_type: usize,
    from_pos: Option<Pos>,
    from_bitboard: BitBoard,
    to_bitboard: BitBoard,
    iterating_piece_type: Option<PieceType>,
}

static PIECE_TYPES: [PieceType; 7] = [
    PieceType::PAWN,
    PieceType::KNIGHT,
    PieceType::MINISTER,
    PieceType::GUARD,
    PieceType::KING,
    PieceType::CANNON,
    PieceType::CHARIOT,
];

impl<'a> MoveGen<'a> {
    pub fn new(board: &mut Board) -> MoveGen {
        MoveGen {
            side_to_move: board.side_to_move(),
            board,
            iter_state: NextPieceType,
            next_piece_type: 0,
            from_pos: None,
            iterating_piece_type: None,
            from_bitboard: BitBoard::new(),
            to_bitboard: BitBoard::new(),
        }
    }

    pub fn get_board(&mut self) -> &mut Board {
        self.board
    }
}

impl<'a> Iterator for MoveGen<'a> {
    type Item = Move;

    fn next(&mut self) -> Option<Move> {
        'outer: loop {
            match self.iter_state {
                NextPieceType => {
                    if self.next_piece_type >= PIECE_TYPES.len() {
                        return None;
                    }
                    let piece_type = PIECE_TYPES[self.next_piece_type];
                    let bitboard = self.board.get_bitboard(self.side_to_move, piece_type);
                    if bitboard.is_empty() {
                        self.iter_state = NextPieceType;
                        self.next_piece_type += 1;
                        continue;
                    } else {
                        self.iter_state = Iterating;
                        self.iterating_piece_type = Some(piece_type);
                        self.from_pos = None;
                        self.from_bitboard = bitboard;
                        continue;
                    }
                }
                Iterating => {
                    let piece_type = self.iterating_piece_type.unwrap();
                    while self.from_pos.is_none() || self.to_bitboard.is_empty() {
                        self.from_pos = self.from_bitboard.mutate_self_return_pos();
                        if self.from_pos.is_none() {
                            self.iter_state = NextPieceType;
                            self.next_piece_type += 1;
                            continue 'outer;
                        }
                        match piece_type {
                            PieceType::PAWN => {
                                self.to_bitboard = pawn_gen_move(
                                    self.board,
                                    self.side_to_move,
                                    self.from_pos.unwrap(),
                                );
                            }
                            PieceType::KING => {
                                self.to_bitboard = king_gen_move(
                                    self.board,
                                    self.side_to_move,
                                    self.from_pos.unwrap(),
                                );
                            }
                            PieceType::GUARD => {
                                self.to_bitboard = guard_gen_move(
                                    self.board,
                                    self.side_to_move,
                                    self.from_pos.unwrap(),
                                );
                            }
                            PieceType::MINISTER => {
                                self.to_bitboard = minister_gen_move(
                                    self.board,
                                    self.side_to_move,
                                    self.from_pos.unwrap(),
                                );
                            }
                            PieceType::KNIGHT => {
                                self.to_bitboard = knight_gen_move(
                                    self.board,
                                    self.side_to_move,
                                    self.from_pos.unwrap(),
                                );
                            }
                            _ => panic!(),
                        }
                        if self.to_bitboard.is_empty() {
                            self.from_pos = None;
                            continue;
                        }
                        break;
                    }
                    debug_assert!(self.to_bitboard.is_not_empty());
                    return Some(Move::new(
                        self.from_pos.unwrap(),
                        self.to_bitboard.mutate_self_return_pos().unwrap(),
                    ));
                }
            }
        }
    }
}

fn pawn_gen_move(board: &Board, side_to_move: Color, pos: Pos) -> BitBoard {
    let mut bitboard = BitBoard::pawn_attack_bitboard(pos, side_to_move);
    let same_side = board.get_side_bitboard(side_to_move);
    bitboard &= !same_side;
    bitboard
}

fn king_gen_move(board: &Board, side_to_move: Color, pos: Pos) -> BitBoard {
    let mut bitboard = BitBoard::king_attack_bitboard(pos);
    let same_side = board.get_side_bitboard(side_to_move);
    bitboard &= !same_side;
    bitboard
}

fn guard_gen_move(board: &Board, side_to_move: Color, pos: Pos) -> BitBoard {
    let mut bitboard = BitBoard::guard_attack_bitboard(pos);
    let same_side = board.get_side_bitboard(side_to_move);
    bitboard &= !same_side;
    bitboard
}

fn minister_gen_move(board: &Board, side_to_move: Color, pos: Pos) -> BitBoard {
    let mut bitboard = BitBoard::minister_attack_bitboard(pos, board.get_both_side_bitboard());
    let same_side = board.get_side_bitboard(side_to_move);
    bitboard &= !same_side;
    bitboard
}

fn knight_gen_move(board: &Board, side_to_move: Color, pos: Pos) -> BitBoard {
    let mut bitboard = BitBoard::knight_attack_bitboard(pos, board.get_both_side_bitboard());
    let same_side = board.get_side_bitboard(side_to_move);
    bitboard &= !same_side;
    bitboard
}
